#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

void handleError(const char* message)
{
    fputs(message, stderr);
    fputs("\n", stderr);
    exit(1);
}

void setNoBlockingMode(int fd)
{
    int flag = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}

const int requestWaitQueueSize = 2;
const int epollSize            = 50;
const int maxBufferSize        = 4;

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    int serverSocket = socket(PF_INET, SOCK_STREAM, 0);

    struct sockaddr_in serverAddress;
    serverAddress.sin_family      = AF_INET;
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port        = htons(atoi(argv[1]));

    if (bind(serverSocket, (struct sockaddr*)(&serverAddress), sizeof(serverAddress)) == -1)
    {
        handleError("bind() error");
    }

    if (listen(serverSocket, requestWaitQueueSize) == -1)
    {
        handleError("listen() error");
    }

    int epollFD = epoll_create(epollSize);

    struct epoll_event* epollEvents = (struct epoll_event*)malloc(sizeof(struct epoll_event) * epollSize);

    struct epoll_event event;
    event.events  = EPOLLIN;
    event.data.fd = serverSocket;
    epoll_ctl(epollFD, EPOLL_CTL_ADD, serverSocket, &event);

    while (1)
    {
        int eventCount = epoll_wait(epollFD, epollEvents, epollSize, -1);
        if (eventCount == -1)
        {
            puts("epoll_wait() error");
            break;
        }
        puts("epoll_wait() return");

        struct sockaddr_in clientAddress;

        char message[maxBufferSize];

        for (int i = 0; i < eventCount; i++)
        {
            if (epollEvents[i].data.fd == serverSocket)
            {
                socklen_t addressSize  = sizeof(clientAddress);
                int       clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &addressSize);

                setNoBlockingMode(clientSocket);

                event.events  = EPOLLIN | EPOLLET;
                event.data.fd = clientSocket;
                epoll_ctl(epollFD, EPOLL_CTL_ADD, clientSocket, &event);
                printf("connect client: %d\n", clientSocket);
            }
            else
            {
                while (1)
                {
                    int stringLength = read(epollEvents[i].data.fd, message, maxBufferSize);
                    if (stringLength == 0)
                    {
                        // close request
                        epoll_ctl(epollFD, EPOLL_CTL_DEL, epollEvents[i].data.fd, NULL);
                        close(epollEvents[i].data.fd);
                        printf("close client: %d \n", epollEvents[i].data.fd);
                    }
                    else if (stringLength < 0)
                    {
                        // stringLength is -1 and errno is EAGAIN means no readable data
                        if (errno == EAGAIN)
                        {
                            break;
                        }
                    }
                    else
                    {
                        // echo
                        printf("server recieved: %s\n", message);
                        write(epollEvents[i].data.fd, message, stringLength);
                    }
                }
            }
        }
    }

    close(serverSocket);
    close(epollFD);

    return 0;
}